// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

// 用于小对象的每个线程（在Go中，Per-P）缓存。
// 这包括一个小对象缓存和本地分配统计数据。
// 不需要锁定，因为它是每个线程（per-P）。
// 
// mcaches是从非GC的内存分配的，因此任何堆指针
// 都必须经过特殊处理。
// 
// go:notinheap 
type mcache struct {
	// 每个malloc上都可以访问以下成员，
	// 因此，为了更好地缓存，将它们分组在这里。
	nextSample uintptr // 分配这么多字节后触发堆示例
	scanAlloc  uintptr // 分配给可扫描堆的字节

	// 没有指针的小对象的分配器缓存。
	// 参见malloc中的“微小分配器”注释。去

	// tiny指向当前小数据块的开头，如果没有当前小数据块，则为
	// nil。
	// 
	// tiny是一个堆指针。由于mcache在非GC的内存中，我们通过在releaseAll中清除它来处理它，在标记
	// 终止期间。
	// 
	// tinyAllocs是拥有此mcache的P执行的微小分配
	// 。
	tiny       uintptr
	tinyoffset uintptr
	tinyAllocs uintptr

	// 其余部分不是在每个malloc上都可以访问的。

	alloc [numSpanClasses]*mspan // spans to allocate from，由spanClass索引

	stackcache [_NumStackOrders]stackfreelist

	// flushGen表示上次刷新此McCache 
	// 的扫描根。如果你脸红了！=姆希普。sweepgen，这张麦卡什中的跨距
	// 已经过时，需要冲洗，这样它们
	// 就可以被清扫了。这是在acquirep中完成的。
	flushGen uint32
}

// gclink是块链接列表中的一个节点，如mlink、
// 但它对垃圾收集器是不透明的。
// GC在收集期间不跟踪指针，
// 并且编译器不会为gclinkptr值的赋值
// 发出写屏障。代码应该将对gclinks 
// 的引用存储为gclinkptr，而不是*gclink。
type gclink struct {
	next gclinkptr
}

// gclinkptr是指向gclink的指针，但对于垃圾收集器来说是不透明的
// 。
type gclinkptr uintptr

// ptr返回p的*gclink表单。
// 结果应该用于访问字段，而不是存储在其他数据结构中。
func (p gclinkptr) ptr() *gclink {
	return (*gclink)(unsafe.Pointer(p))
}

type stackfreelist struct {
	list gclinkptr // 自由堆栈的链接列表
	size uintptr   // 列表中堆栈的总大小
}

// 不包含自由对象的伪mspan。
var emptymspan mspan

func allocmcache() *mcache {
	var c *mcache
	systemstack(func() {
		lock(&mheap_.lock)
		c = (*mcache)(mheap_.cachealloc.alloc())
		c.flushGen = mheap_.sweepgen
		unlock(&mheap_.lock)
	})
	for i := range c.alloc {
		c.alloc[i] = &emptymspan
	}
	c.nextSample = nextSample()
	return c
}

// freemcache释放与此
// mcache关联的资源，并将对象放入自由列表。
// 
// 在某些情况下，无法简单地发布
// 资源，例如统计数据，所以将它们捐赠给
// 另一个mcache（接收者）。
func freemcache(c *mcache) {
	systemstack(func() {
		c.releaseAll()
		stackcache_clear(c)

		// 注意（rsc，rlh）：如果gcworkbuffree回来，我们需要协调
		// 与垃圾收集期间窃取gcworkbufs的行为，以避免
		// 一场工作buf被双重释放的竞赛。
		// gcworkbuffree（c.gcworkbuf）

		lock(&mheap_.lock)
		mheap_.cachealloc.free(unsafe.Pointer(c))
		unlock(&mheap_.lock)
	})
}

// getMCache是一个尝试获取mcache的便利函数。
// 
// 如果我们没有启动或没有P，则返回零。调用方的
// P不能更改，因此我们必须处于不可抢占状态。
func getMCache(mp *m) *mcache {
	// 抓住麦卡什，因为那是统计数据的所在。
	pp := mp.p.ptr()
	var c *mcache
	if pp == nil {
		// 在引导过程中，我们将被调用而不带P，
		// 在这种情况下，我们使用在mallocinit中设置的mcache0。
		// mcache0在引导完成时被清除，
		// 。
		c = mcache0
	} else {
		c = pp.mcache
	}
	return c
}

// refill为c获取一个新的span类spc跨度。此跨度将
// 至少有一个空闲对象。c中的当前量程必须为满。
// 
// 必须在不可抢占的上下文中运行，否则
// c的所有者可能会更改。
func (c *mcache) refill(spc spanClass) {
	// 将当前缓存的范围返回到中心列表。
	s := c.alloc[spc]

	if uintptr(s.allocCount) != s.nelems {
		throw("refill of span with free space remaining")
	}
	if s != &emptymspan {
		// 将此范围标记为不再缓存。
		if s.sweepgen != mheap_.sweepgen+3 {
			throw("bad sweepgen in refill")
		}
		mheap_.central[spc].mcentral.uncacheSpan(s)
	}

	// 从中心列表中获取一个新的缓存范围。
	s = mheap_.central[spc].mcentral.cacheSpan()
	if s == nil {
		throw("out of memory")
	}

	if uintptr(s.allocCount) == s.nelems {
		throw("span has no free space")
	}

	// 指示此范围已缓存，并防止异步
	// 在下一个扫描阶段进行扫描。
	s.sweepgen = mheap_.sweepgen + 3

	// 假设此范围内的所有对象都将分配到
	// mcache中。如果它没有缓存，我们会调整它。
	stats := memstats.heapStats.acquire()
	atomic.Xadduintptr(&stats.smallAllocCount[spc.sizeclass()], uintptr(s.nelems)-uintptr(s.allocCount))

	// 冲洗锡亚龙。
	if spc == tinySpanClass {
		atomic.Xadduintptr(&stats.tinyAllocCount, c.tinyAllocs)
		c.tinyAllocs = 0
	}
	memstats.heapStats.release()

	// 使用相同的假设更新heaplife。
	// 我们在这里的时候，请刷新Scanaloc，因为我们必须给
	// 无论如何都要修改。
	usedBytes := uintptr(s.allocCount) * s.elemsize
	gcController.update(int64(s.npages*pageSize)-int64(usedBytes), int64(c.scanAlloc))
	c.scanAlloc = 0

	c.alloc[spc] = s
}

// allocLarge为大型对象分配一个跨度。
func (c *mcache) allocLarge(size uintptr, noscan bool) *mspan {
	if size+_PageSize < size {
		throw("out of memory")
	}
	npages := size >> _PageShift
	if size&_PageMask != 0 {
		npages++
	}

	// 如有必要，扣除此跨度分配和扫描的积分。mHeap_Alloc还将扫描npage，因此这只
	// 将债务支付到npage页面。
	deductSweepCredit(npages*_PageSize, npages)

	spc := makeSpanClass(0, noscan)
	s := mheap_.alloc(npages, spc)
	if s == nil {
		throw("out of memory")
	}
	stats := memstats.heapStats.acquire()
	atomic.Xadduintptr(&stats.largeAlloc, npages*pageSize)
	atomic.Xadduintptr(&stats.largeAllocCount, 1)
	memstats.heapStats.release()

	// 更新heapLive。
	gcController.update(int64(s.npages*pageSize), 0)

	// 将大跨度放在mcentral扫描列表中，以便背景扫描程序可以看到它。
	mheap_.central[spc].mcentral.fullSwept(mheap_.sweepgen).push(s)
	s.limit = s.base() + size
	heapBitsForAddr(s.base()).initSpan(s)
	return s
}

func (c *mcache) releaseAll() {
	// 借此机会冲洗Scanaloc。
	scanAlloc := int64(c.scanAlloc)
	c.scanAlloc = 0

	sg := mheap_.sweepgen
	dHeapLive := int64(0)
	for i := range c.alloc {
		s := c.alloc[i]
		if s != &emptymspan {
			// 如果跨度未完全分配，请调整nsmallalloc。
			n := uintptr(s.nelems) - uintptr(s.allocCount)
			stats := memstats.heapStats.acquire()
			atomic.Xadduintptr(&stats.smallAllocCount[spanClass(i).sizeclass()], -n)
			memstats.heapStats.release()
			if s.sweepgen != sg+1 {
				// 重新填充gcController中保守计数的未分配插槽。希普利夫。解救这个。
				// 
				// 如果在扫描之前缓存了此范围，则
				// gcController。自从
				// 缓存这个跨度以来，heapLive被完全重新计算，所以我们不会对
				// 过时的跨度执行此操作。
				dHeapLive -= int64(n) * int64(s.elemsize)
			}
			// 将span释放到mcentral。
			mheap_.central[i].mcentral.uncacheSpan(s)
			c.alloc[i] = &emptymspan
		}
	}
	// 清理tinyalloc池。
	c.tiny = 0
	c.tinyoffset = 0

	// 冲洗锡亚龙。
	stats := memstats.heapStats.acquire()
	atomic.Xadduintptr(&stats.tinyAllocCount, c.tinyAllocs)
	c.tinyAllocs = 0
	memstats.heapStats.release()

	// 更新了heapScan和heapLive。
	gcController.update(dHeapLive, scanAlloc)
}

// prepareForSweep会在系统进入新的扫描阶段后刷新c。这必须发生在扫描阶段
// 开始和c的第一次分配之间。
func (c *mcache) prepareForSweep() {
	// 或者，我们不必确保在每个P 
	// 开始世界和在该P上分配之间，我们
	// 可以保持分配为黑色，允许分配像往常一样继续
	// ，在扫描开始时使用参差不齐的屏障，确保扫描所有缓存的跨距，然后禁用
	// 分配黑色。然而，使用这种方法很难避免将标记位溢出到*下一个*GC循环中。
	sg := mheap_.sweepgen
	if c.flushGen == sg {
		return
	} else if c.flushGen != sg-2 {
		println("bad flushGen", c.flushGen, "in prepareForSweep; sweepgen", sg)
		throw("bad flushGen")
	}
	c.releaseAll()
	stackcache_clear(c)
	atomic.Store(&c.flushGen, mheap_.sweepgen) // 与gcStart 
}
